#pragma once

#include <vector>
#include "StringHash.h"
#include "Signals.h"
#include <assert.h>

/// Identifier for types
typedef unsigned int TypeID;

static const TypeID InvalidTypeID = -1;

template <typename TMessage>
TypeID getTypeID();

class MetaConstructor
{
public:
	TypeID signature;

	virtual void* call() = 0;
};

class MetaVariable
{
public:
	const char* name;
	TypeID nameID;
	TypeID typeID; 

	virtual void setValue(void* instance, void* value) = 0;
};

/// @class MetaFunction
/// Container for function information
class MetaFunction
{
public:
	/// Function signature identifier.
	/// Each signature is a type. So signature identifier can be found with getTypeID<signature>()
	/// For example: getTypeID< void() >();
	/// It should be signature of non-member function (free function or static function)
	TypeID signature;

	/// Unique identifier for function name
	TypeID nameID;

	/// String name of the function
	const char* name;
};

class MetaSignal
{
public:
	TypeID nameID;
	TypeID signature;
	const char* name;

	/// @brief	Connects a handler method to the signal.
	///
	/// @param [in]	instance	Owner of signal .
	/// @param [in]	handler 	Handler object.
	/// @param [in]	method  	Handler method.
	///
	/// @return	true if it succeeds, false if it fails.

	virtual bool connect(void* instance, void* handler, MetaFunction* method) = 0;
};

namespace impl
{
	// constructors
	template <class TClass>
	class DefaultMetaConstructor : public MetaConstructor
	{
	public:
		DefaultMetaConstructor()
		{
			signature = getTypeID<void ()>();
		}
		virtual void* call() override
		{
			return new TClass();
		}
	};

	// variables
	template <class TClass, typename T>
	class MetaVar : public MetaVariable
	{
	public:
		T TClass::*variable;

		virtual void setValue(void* instance, void* value) override
		{
			assert(instance);
			assert(value);

			T* temp = (T*)value;
			((TClass*)instance)->*variable = *temp;
		}
	};

	template <class TClass, typename T>
	MetaVariable* makeVariable(TypeID nameID, const char* name,T TClass::*variable)
	{
		impl::MetaVar<TClass, T>* result = new impl::MetaVar<TClass, T>();
		result->variable = variable;
		result->typeID = getTypeID<T>();
		result->nameID = nameID;
		result->name = name;

		return result;
	}

	// functions
	template <typename TSignature>
	class IMetaFunction : public MetaFunction
	{
	};

	template <class TClass, typename TSignature>
	class MemberFunction : public IMetaFunction<TSignature>
	{
	};

	// for void

	template <>
	class IMetaFunction<void()> : public MetaFunction
	{
	public:
		virtual void call(void* instance) = 0;
	};

	template <class TClass>
	class MemberFunction<TClass, void() > : public IMetaFunction<void()>
	{
	public:
		typedef void (TClass::*function_t)();
		function_t _method;

		virtual void call(void* instance) override
		{
			(((TClass*)instance)->*_method)();
		}
	};

	template <class TClass>
	MetaFunction* makeFunction(TypeID nameID, const char* name, void (TClass::*foo)())
	{
		MemberFunction<TClass, void() >* f = new MemberFunction<TClass, void() >();
		f->_method = foo;
		f->name = name;
		f->nameID = nameID;
		f->signature = getTypeID<void()>();
		return f;
	}

	// for void (T)
	template <typename TArg0>
	class IMetaFunction<void(TArg0)> : public MetaFunction
	{
	public:
		virtual void call(void* instance, TArg0 arg0) = 0;
	};

	template <class TClass, typename TArg0>
	class MemberFunction<TClass, void(TArg0) > : public IMetaFunction<void(TArg0)>
	{
	public:
		typedef void (TClass::*function_t)(TArg0);
		function_t _method;

		virtual void call(void* instance, TArg0 arg0) override
		{
			(((TClass*)instance)->*_method)(arg0);
		}
	};

	template <class TClass, typename TArg0>
	MetaFunction* makeFunction(TypeID nameID, const char* name, void (TClass::*foo)(TArg0))
	{
		MemberFunction<TClass, void(TArg0) >* f = new MemberFunction<TClass, void(TArg0) >();
		f->_method = foo;
		f->name = name;
		f->nameID = nameID;
		f->signature = getTypeID<void(TArg0)>();
		return f;
	}

	template <class TClass, typename TSignature>
	class TMetaSignal : public MetaSignal
	{
	};

	template <class TClass>
	class TMetaSignal <TClass, void ()> : public MetaSignal
	{
	public:
		Signal<void()> TClass::* signal;

		void call(void* handler, MetaFunction* foo)
		{
			IMetaFunction<void()>* f = (IMetaFunction<void()>*)foo;
			f->call(handler);
		}
		virtual bool connect(void* instance, void* handler, MetaFunction* foo) override
		{
			(((TClass*)instance)->*signal).connect(bind(&TMetaSignal<TClass, void()>::call, this, handler, foo));
			return true;
		}
	};

	template <class TClass, typename TArg0>
	class TMetaSignal <TClass, void (TArg0)> : public MetaSignal
	{
	public:
		Signal<void(TArg0)> TClass::* signal;

		void call(void* handler, MetaFunction* foo, TArg0 arg0)
		{
			IMetaFunction<void(TArg0)>* f = (IMetaFunction<void(TArg0)>*)foo;
			f->call(handler, arg0);
		}
		virtual bool connect(void* instance, void* handler, MetaFunction* foo) override
		{
			(((TClass*)instance)->*signal).connect(bind(&TMetaSignal<TClass, void(TArg0)>::call, this, handler, foo, placeholders::_1));
			return true;
		}
	};

	template <class TClass, typename TSignature>
	MetaSignal* makeSignal(TypeID nameID, const char* name, Signal<TSignature> TClass::*signal)
	{
		TMetaSignal<TClass, TSignature>* ms = new TMetaSignal<TClass, TSignature>();
		ms->nameID = nameID;
		ms->name = name;
		ms->signature = getTypeID<TSignature>();
		ms->signal = signal;

		return ms;
	}
}

class MetaClass
{
public:
	static MetaClass* getMetaClass(TypeID type);
	static MetaClass* getMetaClass(const char* className);
	MetaClass(const char* cName, TypeID type);

	TypeID typeID;
	const char* name;
	MetaClass* base;
	std::vector<MetaConstructor*> constructors;
	std::vector<MetaFunction*> functions;
	std::vector<MetaSignal*> signals;
	std::vector<MetaVariable*> variables;

	bool isSubclass(TypeID type) const;

	template<class TClass>
	bool isSubclass() const
	{
		TypeID type = getTypeID<TClass>();
		return isSubclass(type);
	}

	MetaFunction* getMethod(TypeID nameID, TypeID signature, bool searchInBase=true ) const;

	MetaVariable* getVariable(TypeID nameID) const;

	MetaSignal* getSignal(TypeID nameID) const;

	MetaConstructor* getConstructor(TypeID signatureID = getTypeID< void() >());

	template <typename T>
	void setVariable(void* instance, TypeID nameID, const T& value)
	{
		auto variable = getVariable(nameID);
		variable->setValue(instance, (void*)&value);
	}

	// Creates object with default constructor
	void* construct();

	void callMember(void* instance, TypeID nameID)
	{
		MetaFunction* f = getMethod(nameID, getTypeID<void()>(), true);
		if (f)
			return ((impl::IMetaFunction<void()>*)f)->call(instance); 
	}

	template <typename TArg0>
	void callMember(void* instance, TypeID nameID, TArg0 arg0)
	{
		MetaFunction* f = getMethod(nameID, getTypeID<void(TArg0)>(), true);
		if (f)
			return ((impl::IMetaFunction<void(TArg0)>*)f)->call(instance, arg0); 
	}
};

/// Specify that class has a meta-table
/// All class members should be added to meta-table manually
#define mtMETA_DEC \
	public: \
		static MetaClass* _metaTable(); \
		virtual MetaClass* getMetaTable() const;

/// Start meta-table implementation
#define  mtBEGIN(className) \
	namespace _meta\
	{ \
		class className##MetaClass : public MetaClass \
		{ \
		public: \
			typedef className class_t; \
			className##MetaClass(const char* name, TypeID type); \
		}; \
	}\
	\
	MetaClass* className::getMetaTable() const {return className::_metaTable();}\
	MetaClass* className::_metaTable() \
	{ \
		static MetaClass* table = new _meta::className##MetaClass(#className, getTypeID<className>()); \
		return table; \
	} \
	namespace _meta\
	{ \
		className##MetaClass::className##MetaClass(const char* name, TypeID type) : MetaClass(name, type)\
		{ \

/// End of meta-table 
#define mtEND \
		} \
	}

/// Specify base class
#define mtBASE(className) \
	base = className::_metaTable()

/// Specify method
/// Method can not be overloaded
#define mtMETHOD(foo) \
	functions.push_back(impl::makeFunction(ECS::getStaticStringHash(#foo), #foo, &class_t::##foo))

#define mtSIGNAL(ev) \
	signals.push_back(impl::makeSignal(ECS::getStaticStringHash(#ev), #ev, &class_t::ev))

#define  mtVARIABLE(variable) \
	variables.push_back(impl::makeVariable(ECS::getStaticStringHash(#variable), #variable, &class_t::variable))

#define mtCONSTRUCTOR(...)	\
	constructors.push_back(new impl::DefaultMetaConstructor<class_t>())

#define  mtGET_METHOD(table, foo, signature) \
	(table->getMethod(ECS::getStaticStringHash(#foo), getTypeID<signature>(), true))

#define  mtSET_VARIABLE(instance, variable, value) \
	(instance->getMetaTable()->setVariable(instance, ECS::getStaticStringHash(#variable), value))

/// Call method for instance with given meta-table
#define mtCALL_EX(table, instance, ...)\
	table->callMember(instance, __VA_ARGS__)

/// Call method
#define mtCALL(instance, method, ...) \
	mtCALL_EX(instance->getMetaTable(), instance, ECS::getStaticStringHash(#method), __VA_ARGS__)


// implementation
namespace impl
{
	TypeID getNextTypeID();
}

template <typename TMessage>
TypeID getTypeID()
{
	static TypeID type = impl::getNextTypeID();
	return type;
}